feat(metadata-protocol): enforce package namespace prefix for Studio-authored packages#2694
Merged
Conversation
…authored packages The protocol requires every object.name to carry its package's manifest.namespace prefix (crm_account); defineStack enforces this at compile time. Studio/runtime- authored packages never took that path and were created without a namespace at all, so the rule was silently inert and objects published with bare, collision- prone names (e.g. two packages could each publish `leave_request`). - installPackage: derive a default namespace from the package id when the manifest declares none (com.example.leave → leave) and persist it on the manifest; an explicit namespace always wins (HotCRM's crm is untouched). - publishPackageDrafts: reject any object draft missing the <ns>_ prefix before promoting anything (atomic), with an actionable "rename to ..." message. Namespace-less packages are grandfathered, mirroring defineStack. The per-object check and id→namespace derivation are extracted to @objectstack/spec/kernel as the single source shared by defineStack and the runtime publish path, so the two enforcement points cannot drift. Found by dogfooding package-first authoring: a Studio-built package (com.example.leave) persisted namespace=null, so its object leave_request had no prefix and published with zero validation. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
Contributor
📓 Docs Drift CheckThis PR changes 3 package(s): 99 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:
|
Additive-only: registers validateObjectNamespacePrefix and deriveNamespaceFromPackageId (both new exports from @objectstack/spec/kernel) in the frozen API-surface snapshot. Consistent with the minor bump already in the changeset. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Why
Per the protocol (ADR-0028 current-state contract;
manifest.zodnamespace), everyobject.namein a package must carry the package's namespace prefix (crm_account,showcase_project).defineStack()enforces this at compile time viavalidateNamespacePrefix.Studio / runtime-authored packages bypassed it entirely: they are created without a
namespaceat all, and neither the runtime create path nor the publish path ran the check. So an admin building a package in Studio got objects with bare, collision-prone names (two packages could each publishleave_request→ silent table/route collision, exactly the risk ADR-0028/ADR-0048 flag).Found by dogfooding: a Studio-built package
com.example.leavepersistednamespace: null, and its objectleave_requestpublished with zero prefix validation.What
Two runtime changes, both in
@objectstack/metadata-protocol:installPackagederives a default namespace from the package id (com.example.leave→leave) when the manifest declares none, and persists it on the singlemanifestobject shared by the in-memory registry and the durablesys_packagesrow. An explicitly declared namespace always wins — HotCRM'scrm(which differs from its id's last segmenthotcrm) is untouched.publishPackageDraftsrejects any object draft whose name lacks the<ns>_prefix, before promoting anything (the publish is atomic), with an actionable message. Namespace-less legacy packages are grandfathered — mirroringdefineStack, the rule is not invented at enforcement time; the default is derived+persisted once at install.The per-object check and the id→namespace derivation are extracted into
@objectstack/spec/kernel(validateObjectNamespacePrefix,deriveNamespaceFromPackageId) as the single source shared bydefineStackand the runtime publish path, so the two enforcement points cannot drift.validateNamespacePrefixinstack.zod.tsis refactored onto the shared helper (behavior-preserving).Verification
Unit/regression (all green):
spec:namespace-prefix.test.ts(10 new) +stack.test.tsnamespace suite — 95 passed.objectql:protocol-publish-package-drafts.test.ts(+3: reject / allow-prefixed / grandfather),protocol-install-package.test.ts(+2: derive / explicit-wins), plus install/lifecycle/collision suites — no regressions.Live end-to-end on a running showcase app (rebuild + restart):
com.example.hr→ namespace auto-derived tohr✅ticket→ publish rejected:Object 'ticket' is missing the package namespace prefix. Rename it to 'hr_ticket' (namespace = 'hr').✅pto_requestunder namespacepto→ publishes ✅com.example.leavestill reads;showcaseunaffected ✅Follow-up (objectui, separate)
Studio's "新建软件包" dialog should surface the namespace (default-derived, editable) and "新建对象" should auto-prefix the identifier, so authors never hit the publish-time rejection. Filed separately.
🤖 Generated with Claude Code